package com.sh.data.engine.domain.normalization.modeldesign.cdm.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.sh.data.engine.common.exception.BusinessException;
import com.sh.data.engine.common.util.ServletUtils;
import com.sh.data.engine.domain.base.model.PageResult;
import com.sh.data.engine.domain.dataasset.service.TblPropService;
import com.sh.data.engine.domain.normalization.modeldesign.cdm.model.domain.TblRelationDomain;
import com.sh.data.engine.domain.normalization.modeldesign.cdm.model.mapper.TblRelationEntityMapper;
import com.sh.data.engine.domain.normalization.modeldesign.cdm.model.param.TblRelationParam;
import com.sh.data.engine.domain.normalization.modeldesign.cdm.model.param.TblRelationRemoveParam;
import com.sh.data.engine.domain.normalization.modeldesign.cdm.service.TblRelationService;
import com.sh.data.engine.domain.normalization.modeldesign.model.model.domain.ModelListDomain;
import com.sh.data.engine.domain.normalization.modeldesign.model.model.param.SaveModelParam;
import com.sh.data.engine.domain.normalization.modeldesign.model.service.ModelService;
import com.sh.data.engine.repository.dao.dataasset.entity.TblPropEntity;
import com.sh.data.engine.repository.dao.normalization.modeldesign.cdm.TblRelationMapper;
import com.sh.data.engine.repository.dao.normalization.modeldesign.cdm.entity.TblRelationEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class TblRelationServiceImpl extends ServiceImpl<TblRelationMapper, TblRelationEntity>
    implements TblRelationService {

    @Value("${setting.table.rel.max.floor:3}")
    private int maxFloor;

    @Lazy
    @Autowired
    private TblPropService tblPropService;

    @Lazy
    @Autowired
    private ModelService modelService;

    @Autowired
    private TblRelationEntityMapper tblRelationEntityMapper;

    @Override
    public List<TblRelationDomain> getRelListLeftForward(Long projectId, Long mainTableId) {
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getProjectId, projectId)
                    .eq(TblRelationEntity::getMainTableId, mainTableId)
                    .orderByAsc(TblRelationEntity::getCreateTime));
        return Convert.toList(TblRelationDomain.class, list);
    }

    @Override
    public List<TblRelationDomain> getRelListRightForward(Long projectId, Long foreignTableId) {
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getProjectId, projectId)
                    .eq(TblRelationEntity::getForeignTableId, foreignTableId)
                    .orderByAsc(TblRelationEntity::getCreateTime));
        return Convert.toList(TblRelationDomain.class, list);
    }

    @Override
    public List<TblRelationDomain> selectByForeign(
        Long projectId, Long foreignTableId, String foreignTableField) {
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getProjectId, projectId)
                    .eq(TblRelationEntity::getForeignTableId, foreignTableId)
                    .eq(TblRelationEntity::getForeignTableField, foreignTableField));
        return Convert.toList(TblRelationDomain.class, list);
    }

    @Override
    public boolean deleteByTable(Long projectId, Long mainTableId, Long foreignTableId) {
        return this.remove(
            new LambdaQueryWrapper<TblRelationEntity>()
                .eq(TblRelationEntity::getProjectId, projectId)
                .eq(TblRelationEntity::getForeignTableId, foreignTableId)
                .eq(TblRelationEntity::getMainTableId, mainTableId));
    }

    @Override
    public boolean deleteByTableField(
        Long projectId,
        Long mainTableId,
        String mainTableField,
        Long foreignTableId,
        String foreignTableField) {
        return this.remove(
            new LambdaQueryWrapper<TblRelationEntity>()
                .eq(TblRelationEntity::getProjectId, projectId)
                .eq(TblRelationEntity::getMainTableId, mainTableId)
                .eq(TblRelationEntity::getForeignTableId, foreignTableId)
                .eq(TblRelationEntity::getMainTableField, mainTableField)
                .eq(TblRelationEntity::getForeignTableField, foreignTableField));
    }

    /**
     * 根据主表id查询
     *
     * @param mainTblId
     * @return
     */
    @Override
    public List<TblRelationDomain> getByMainTblId(Long mainTblId) {
        if (Objects.isNull(mainTblId)) {
            return Collections.emptyList();
        }
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getMainTableId, mainTblId));
        return Convert.toList(TblRelationDomain.class, list);
    }

    /**
     * 根据外表id查询
     *
     * @param foreignTblId
     * @return
     */
    @Override
    public List<TblRelationDomain> getByForeignTblId(Long foreignTblId) {
        if (Objects.isNull(foreignTblId)) {
            return Collections.emptyList();
        }
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getForeignTableId, foreignTblId));
        return Convert.toList(TblRelationDomain.class, list);
    }

    // 修改表结构的时候同步刷新表关联关系
    @Override
    public boolean refreshTblRelation(SaveModelParam saveModelParam) {
        Long tblId = saveModelParam.getTblId();

        List<String> fields =
            saveModelParam.getFields().stream().map(f -> f.getFieldName()).collect(Collectors.toList());

        List<TblRelationDomain> mainTblRelations = this.getByMainTblId(tblId);
        List<Long> mainIds =
            mainTblRelations.stream()
                .filter(relation -> !fields.contains(relation.getMainTableField()))
                .map(r -> r.getId())
                .collect(Collectors.toList());

        List<TblRelationDomain> foreignTblRelations = this.getByForeignTblId(tblId);
        List<Long> foreignIds =
            foreignTblRelations.stream()
                .filter(relation -> !fields.contains(relation.getForeignTableField()))
                .map(r -> r.getId())
                .collect(Collectors.toList());

        mainIds.addAll(foreignIds);

        if (CollectionUtils.isNotEmpty(mainIds)) {
            this.removeByIds(mainIds);
        }

        return true;
    }

    /**
     * 根据ids查列表
     *
     * @param ids
     * @return
     */
    @Override
    public List<TblRelationDomain> getListByIds(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<TblRelationEntity> list = this.listByIds(ids);
        return Convert.toList(TblRelationDomain.class, list);
    }

    @Override
    public void updateFieldName(Long tblId, String oldField, String newField) {
        // TODO
    }

    @Override
    public List<TblRelationDomain> getTblRelationList(Long tableId) {
        List<TblRelationDomain> res = Lists.newArrayList();
        Long projectId = ServletUtils.getProjectId();
        List<TblRelationDomain> relList = this.getRelListLeftForward(projectId, tableId);
        Map<String, List<TblRelationDomain>> collect =
            relList.stream()
                .collect(Collectors.groupingBy(re -> re.getForeignTableId() + "_" + re.getUuid()));
        collect.forEach(
            (key, val) -> {
                TblRelationDomain cdmTblRelationDomain = val.get(0);
                Set<String> mainTableFields =
                    val.stream().map(v -> v.getMainTableField()).collect(Collectors.toSet());
                Set<String> foreignTableFields =
                    val.stream().map(v -> v.getForeignTableField()).collect(Collectors.toSet());
                cdmTblRelationDomain.setMainTableFields(mainTableFields);
                cdmTblRelationDomain.setForeignTableFields(foreignTableFields);
                res.add(cdmTblRelationDomain);
            });
        return res.stream()
            .sorted((r1, r2) -> r1.getId().compareTo(r2.getId()))
            .collect(Collectors.toList());
    }

    @Override
    public List<TblRelationDomain> getDiagramTblRelationList(Long projectId, Long tableId) {
        List<TblRelationDomain> right = this.getRelListRightForward(projectId, tableId);
        List<TblRelationDomain> left = this.getRelListLeftForward(projectId, tableId);
        List<TblRelationDomain> all = Lists.newArrayListWithExpectedSize(right.size() + left.size());
        if (!CollectionUtils.isEmpty(right)) {
            all.addAll(right);
        }
        if (!CollectionUtils.isEmpty(left)) {
            all.addAll(left);
        }
        // 把tableId重置为主表id，并表明方向
//    List<TblRelationDomain> list = ConvertUtil.copyProperties(all, TblRelationDomain.class);
        for (TblRelationDomain tem : all) {
            if (Objects.equals(tableId, tem.getForeignTableId())) {
                Long mainTableId = tem.getMainTableId();
                String mainTableField = tem.getMainTableField();
                tem.setMainTableId(tem.getForeignTableId());
                tem.setMainTableField(tem.getForeignTableField());
                tem.setIsFromMain(true);
                tem.setForeignTableId(mainTableId);
                tem.setForeignTableField(mainTableField);
            }
        }
        return all;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveTblRelation(List<TblRelationParam> relationList) {
        List<TblRelationParam> relations = Lists.newArrayList();
        relationList.forEach(
            re -> {
                if (re.getMainTableFields().size() > 1 && re.getForeignTableFields().size() > 1) {
                    throw new BusinessException("存在多对多的映射关系，请检查后重试！");
                }
                List<String> mainTableFields = re.getMainTableFields();
                String uuidStr = UUID.randomUUID().toString();
                re.setUuid(uuidStr);
                for (String mainField : mainTableFields) {
                    re.setMainTableField(mainField);
                    List<String> foreignTableFields = re.getForeignTableFields();
                    for (String foreignFiled : foreignTableFields) {
                        TblRelationParam clone = ObjectUtil.clone(re);
                        clone.setForeignTableField(foreignFiled);
                        relations.add(clone);
                    }
                }
            });
        Map<Long, List<TblRelationParam>> relationMap =
            relations.stream().collect(Collectors.groupingBy(TblRelationParam::getForeignTableId));
        relationMap.forEach(
            (key, val) -> {
                this.save(val);
            });
        return Boolean.TRUE;
    }

    private boolean save(List<TblRelationParam> relationList) {
        var projectId = ServletUtils.getProjectId();
        Map<String, List<TblRelationParam>> foreignTblMap =
            relationList.stream()
                .collect(Collectors.groupingBy(TblRelationParam::getForeignTableField));
        foreignTblMap.forEach(
            (forgignTblField, tRelations) -> {
                if (tRelations.stream().anyMatch(t -> t.getType() == 1) && tRelations.size() > 1) {
                    throw new BusinessException(forgignTblField + " 存在一对多关系，请勿重复关联");
                }
            });
        Map<String, List<TblRelationParam>> mainTblMap =
            relationList.stream().collect(Collectors.groupingBy(TblRelationParam::getMainTableField));
        mainTblMap.forEach(
            (mainTblField, tRelations) -> {
                if (tRelations.stream().anyMatch(t -> t.getType() == 2) && tRelations.size() > 1) {
                    throw new BusinessException(mainTblField + " 存在多对一关系，请勿重复关联");
                }
            });

        relationList.forEach(e -> e.setProjectId(projectId));
        Long mainTableId = relationList.get(0).getMainTableId();
        Long foreignTableId = relationList.get(0).getForeignTableId();

        // 先删除该主表与外表的所有字段关系
        this.deleteByTable(projectId, mainTableId, foreignTableId);
        // 再保存新的全量的主表与外表的字段关系
        // 批量插入
        return this.saveBatch(tblRelationEntityMapper.map(relationList));
    }

    private boolean saveV1(List<TblRelationParam> relationList) {
        var projectId = ServletUtils.getProjectId();
        relationList.forEach(e -> e.setProjectId(projectId));
        Long mainTableId = relationList.get(0).getMainTableId();
        Long foreignTableId = relationList.get(0).getForeignTableId();
        if (Objects.equals(mainTableId, foreignTableId)) {
            throw new BusinessException("主表与外表需不一致");
        }
        // 只允许单向关系：向右，外表-》主表
        // 禁止循环依赖：主表作为起点，不能向右找到外表
        List<String> way = theWayFromMainTableToForeignTable(projectId, mainTableId, foreignTableId);
        if (CollectionUtils.isNotEmpty(way)) {
            throw new BusinessException("存在循环依赖: " + String.join(" -> ", way));
        }
        // 外表向左的层次+主表向右的层次+即将关联后的1层，不能大于最大允许层次
        int floorCount =
            countRelFloors(projectId, foreignTableId, false)
                + countRelFloors(projectId, mainTableId, true);
        if ((floorCount + 1) > maxFloor) {
            throw new BusinessException("超过最大允许的关联层次：当前层次已达: " + floorCount + "层");
        }
        // 外表的一个字段，最多只能关联一次
        TblRelationParam unValid = this.isOneToOne(relationList);
        if (unValid != null) {
            throw new BusinessException(
                "外表的一个字段最多只能关联一次"
                    + getTableNameById(unValid.getForeignTableId())
                    + '.'
                    + unValid.getForeignTableField());
        }
        // 先删除该主表与外表的所有字段关系
        this.deleteByTable(projectId, mainTableId, foreignTableId);
        // 再保存新的全量的主表与外表的字段关系
        // 批量插入
        return this.saveBatch(tblRelationEntityMapper.map(relationList));
    }

    @Override
    public List<String> theWayFromMainTableToForeignTable(
        Long projectId, Long mainTableId, Long foreignTableId) {
        if (Objects.equals(mainTableId, foreignTableId)) {
            return Collections.emptyList();
        }
        // 记录未搜索的
        Queue<Long> queue = new LinkedList<>();
        queue.add(mainTableId);
        // 记录已搜索的
        Set<Long> set = new HashSet<>();
        set.add(mainTableId);
        // 记录子->父的关系，用于最后出结果时，一路向上，反查路径
        Map<Long, Long> father = new HashMap<>(16);
        while (!queue.isEmpty()) {
            Long tableId = queue.poll();
            // 找到该表所有的关联表
            List<Long> relList = getMainTblIdRightForward(projectId, tableId);
            for (Long child : relList) {
                if (!set.contains(child)) {
                    // 不重复，才加入
                    set.add(child);
                    queue.add(child);
                    father.put(child, tableId);
                }
            }
            if (relList.contains(foreignTableId)) {
                // 第一次遇到满足的，就直接返回
                return getTableNameById(getWayFromFatherMap(father, mainTableId, foreignTableId));
            }
        }
        return Collections.emptyList();
    }

    private List<Long> getMainTblIdRightForward(Long projectId, Long tableId) {
        List<TblRelationDomain> relList = this.getRelListRightForward(projectId, tableId);
        if (CollectionUtils.isEmpty(relList)) {
            return Collections.emptyList();
        } else {
            return relList.stream()
                .map(TblRelationDomain::getMainTableId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        }
    }

    private List<Long> getMainTblIdLeftForward(Long projectId, Long tableId) {
        List<TblRelationDomain> relList = this.getRelListLeftForward(projectId, tableId);
        if (CollectionUtils.isEmpty(relList)) {
            return Collections.emptyList();
        } else {
            return relList.stream()
                .map(TblRelationDomain::getForeignTableId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        }
    }

    private int countRelFloors(Long projectId, Long tableId, boolean isRightForward) {
        Queue<Long> queue = new LinkedList<>();
        queue.add(tableId);
        Set<Long> set = new HashSet<>();
        set.add(tableId);
        // 记录该表所处层次
        Map<Long, Integer> map = new HashMap<>(16);
        // 根节点设置为第一层
        map.put(tableId, 1);
        while (!queue.isEmpty()) {
            Long topTableId = queue.poll();
            Integer thisTableFloor = map.get(topTableId);
            // 该表的关联表，应该所处的层次
            Integer childTableFloor = ++thisTableFloor;
            List<Long> relList =
                isRightForward
                    ? getMainTblIdRightForward(projectId, topTableId)
                    : getMainTblIdLeftForward(projectId, topTableId);
            for (Long child : relList) {
                if (!set.contains(child)) {
                    set.add(child);
                    queue.add(child);
                    map.put(child, childTableFloor);
                }
            }
        }
        // 最大层次减1，即为向左（向右）的箭头数
        return map.values().stream().mapToInt(Integer::intValue).max().orElse(1) - 1;
    }

    private static List<Long> getWayFromFatherMap(
        Map<Long, Long> father, Long mainTableId, Long foreignTableId) {
        List<Long> way = new ArrayList<>();
        way.add(foreignTableId);
        Long temp = father.get(foreignTableId);
        way.add(temp);
        int max = father.size();
        int count = 0;
        while (!Objects.equals(temp, mainTableId)) {
            count++;
            temp = father.get(temp);
            way.add(temp);
            if (count > max) {
                log.error(
                    "计算主表到外表的路径异常，主表=[{}]，外表=[{}]，子->父关系=[{}]",
                    mainTableId,
                    foreignTableId,
                    JSON.toJSONString(father));
                throw new BusinessException("计算路径异常");
            }
        }
        return Lists.reverse(way);
    }

    private String getTableNameById(Long tableId) {
        TblPropEntity tblProp = tblPropService.getTblPropById(tableId);
        if (Objects.nonNull(tblProp)) {
            return tblProp.getTableName();
        }
        return null;
    }

    private List<String> getTableNameById(List<Long> tableIdList) {
        List<String> list = Lists.newArrayListWithExpectedSize(tableIdList.size());
        for (Long tableId : tableIdList) {
            TblPropEntity tblProp = tblPropService.getTblPropById(tableId);
            if (Objects.nonNull(tblProp)) {
                list.add(tblProp.getTableName());
            }
        }
        return list;
    }

    /**
     * 判断外表字段是否有且仅被关联过一次
     *
     * @param relationList
     * @return
     */
    private TblRelationParam isOneToOne(List<TblRelationParam> relationList) {
        // 外表的一个字段，最多只能关联一次
        // 先从入参判断
        Set<String> exist = new HashSet<>();
        for (TblRelationParam rel : relationList) {
            if (exist.contains(rel.getForeignTableField())) {
                return rel;
            } else {
                exist.add(rel.getForeignTableField());
            }
        }
        // 再去查数据库判断
        for (TblRelationParam rel : relationList) {
            // 根据外表查全量
            List<TblRelationDomain> list =
                this.selectByForeign(
                    rel.getProjectId(), rel.getForeignTableId(), rel.getForeignTableField());
            if (CollectionUtils.isEmpty(list)) {
                continue;
            }
            // 去除已知的即将保存的关系
            list =
                list.stream()
                    .filter(
                        e -> {
                            if (Objects.equals(e.getMainTableId(), rel.getMainTableId())
                                && Objects.equals(e.getMainTableField(), rel.getMainTableField())) {
                                return false;
                            }
                            return true;
                        })
                    .collect(Collectors.toList());
            // 还有，就表示会插入重复
            if (!CollectionUtils.isEmpty(list)) {
                return rel;
            }
        }
        return null;
    }

    @Override
    public boolean deleteDiagramTblRelation(TblRelationRemoveParam relation) {
        Long projectId = ServletUtils.getProjectId();
        this.deleteByTable(projectId, relation.getMainTableId(), relation.getForeignTableId());
        this.deleteByTable(projectId, relation.getForeignTableId(), relation.getMainTableId());
        return Boolean.TRUE;
    }

    @Override
    public long countTblRelation(Long projectId, Long tblId) {
        long count =
            this.count(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getProjectId, projectId)
                    .and(
                        e ->
                            e.eq(TblRelationEntity::getMainTableId, tblId)
                                .or()
                                .eq(TblRelationEntity::getForeignTableId, tblId)));
        return count;
    }

    @Override
    public List<TblRelationEntity> listTblRelation(Long projectId, List<Long> tblIds) {
        if (CollectionUtils.isEmpty(tblIds)) {
            return Collections.emptyList();
        }
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getProjectId, projectId)
                    .and(
                        e ->
                            e.in(TblRelationEntity::getMainTableId, tblIds)
                                .or()
                                .in(TblRelationEntity::getForeignTableId, tblIds)));
        return list;
    }

    public List<TblRelationEntity> listTblRelation(
        Long projectId, Long mainTblId, Long foreignTblId) {
        if (Objects.isNull(mainTblId) || Objects.isNull(foreignTblId)) {
            return Collections.emptyList();
        }
        List<TblRelationEntity> list =
            this.list(
                new LambdaQueryWrapper<TblRelationEntity>()
                    .eq(TblRelationEntity::getProjectId, projectId)
                    .eq(TblRelationEntity::getMainTableId, mainTblId)
                    .eq(TblRelationEntity::getForeignTableId, foreignTblId));
        return list;
    }

    public PageResult<ModelListDomain> getTablePage(
        Integer pageNum, Integer pageSize, List<String> modelType, String search) {
        PageResult<ModelListDomain> tablePage =
            modelService.getTablePage(pageNum, pageSize, modelType, search);
        List<ModelListDomain> resultList = tablePage.getResult();
        List<Long> tblIds = resultList.stream().map(r -> r.getTblId()).collect(Collectors.toList());

        Long projectId = ServletUtils.getProjectId();
        List<TblRelationEntity> tblRelations =
            this.listTblRelation(projectId, tblIds);
        Set<Long> relationIds = new HashSet<>();
        tblRelations.stream()
            .forEach(
                tr -> {
                    relationIds.add(tr.getMainTableId());
                    relationIds.add(tr.getForeignTableId());
                });
        resultList.forEach(
            re -> {
                if (relationIds.contains(re.getTblId())) {
                    re.setHashRelation(Boolean.TRUE);
                }
            });
        tablePage.setResult(resultList);
        return tablePage;
    }
}
